package xman.mstruts.annotation;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.Properties;

import org.apache.commons.lang.ClassUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;

public class PropertyProvider extends PropertyPlaceholderConfigurer {

    private Properties props;
    
    private List<String> staticClazzs;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        super.postProcessBeanFactory(beanFactory);
        try {
            props = mergeProperties();
        } catch (IOException ex) {
            throw new BeanInitializationException("Could not load properties", ex);
        }
        injectStaticClass();
    }
    
    // field.set(bean,val),当field是静态字段时，则忽略bean变量，所以同时传入clazz和bean
    public static boolean setField(Class<?> clazz, Object bean, String name, String value) {
        // 反射调用setter
        try {
            Method method = getSetterMethodForField(clazz, bean, name);
            if (method != null) {
                method.invoke(bean, new Object[] { convertValue(value, method.getParameterTypes()[0]) });
                return true;
            }
        } catch (Exception e) {
        }
        // 强制修改属性值
        try {
            Field field = clazz.getDeclaredField(name);
            if (!(bean == null ^ Modifier.isStatic(field.getModifiers()))) {
                field.setAccessible(true);
                field.set(bean, convertValue(value, field.getType()));
                return true;
            }
        } catch (Exception e) {
        }
        return false;
    }
    
    public static Object getField(Class<?> clazz, Object bean, String name) {
        try {
            Field field = clazz.getDeclaredField(name);
            if (!(bean == null ^ Modifier.isStatic(field.getModifiers()))) {
                field.setAccessible(true);
                return field.get(bean);
            }
        } catch (Exception e) {
        }
        return "FAILURE";
    }
    
    private static Method getSetterMethodForField(Class<?> clazz, Object bean, String fieldName) {
        try {
            String setterName = "set";
            setterName += Character.toUpperCase(fieldName.charAt(0));
            setterName += fieldName.substring(1);
            Method method = getMethod(clazz, setterName);
            if (method.getParameterTypes().length == 1 && !(bean == null ^ Modifier.isStatic(method.getModifiers()))) {
                method.setAccessible(true); // 修改访问属性
                return method;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    
    private static Method getMethod(Class<?> clazz, String methodName) throws NoSuchMethodException {
        for (Method method : clazz.getDeclaredMethods()) {
            if (method.getName().equals(methodName) && method.getParameterTypes().length == 1)
                return method;
        }
        throw new NoSuchMethodException(clazz + "." + methodName);
    }
    
    public static Object convertValue(String value, Class<?> type) {
        if (value == null)
            return null;
        Object obj = null;
        if (type == String.class)
            return value;
        else {
            type = ClassUtils.primitiveToWrapper(type);
            try {
                obj = type.getMethod("valueOf", String.class).invoke(null, value);
            } catch (Exception e) {
                try {
                    obj = type.getConstructor(String.class).newInstance(value);
                } catch (Exception ee) {
                    obj = null;
                }
            }
        }
        return obj;
    }
    
    public void injectFields(Class<?> clazz, Object bean) {
        if (props == null)
            return;
        for (Field field : clazz.getDeclaredFields()) {
            if (bean == null ^ Modifier.isStatic(field.getModifiers()))
                continue;
            if (!field.isAnnotationPresent(Property.class))
                continue;
            Property property = field.getAnnotation(Property.class);
            field.setAccessible(true);
            Method setterMethod = getSetterMethodForField(clazz, bean, field.getName());
            String value = null;
            if (property.value().length() > 0) {
                value = props.getProperty(property.value());
            } else if (property.text().length() > 0) {
                value = parseStringValue(property.text(), props, null);
            }
            Object obj = convertValue(value, setterMethod == null ? field.getType()
                    : setterMethod.getParameterTypes()[0]);
            if (obj != null) {
                try {
                    if (setterMethod == null)
                        field.set(bean, obj);
                    else
                        setterMethod.invoke(bean, new Object[] { obj });
                } catch (Exception e) {
                    throw new BeanInitializationException("Could not set poperty " + field.getName() + " of class "
                            + clazz + " with " + value);
                }
            }
        }
    }
    
    public void injectMethods(Class<?> clazz, Object bean) {
        if (props == null)
            return;
        for (Method method : clazz.getDeclaredMethods()) {
            if (bean == null ^ Modifier.isStatic(method.getModifiers()))
                continue;
            if (!method.isAnnotationPresent(Property.class))
                continue;
            if (method.getParameterTypes().length != 1)
                continue;
            Property property = method.getAnnotation(Property.class);
            method.setAccessible(true);
            String value = null;
            if (property.value().length() > 0) {
                value = props.getProperty(property.value());
            } else if (property.text().length() > 0) {
                value = parseStringValue(property.text(), props, null);
            }
            Object obj = convertValue(value, method.getParameterTypes()[0]);
            if (obj != null) {
                try {
                    method.invoke(bean, obj);
                } catch (Exception e) {
                    throw new BeanInitializationException("Could not set poperty " + method.getName() + " of class "
                            + clazz + " with " + value);
                }
            }
        }
    }
    
    private void injectStaticClass() throws BeansException {
        if (staticClazzs == null)
            return;
        for (String clzName : staticClazzs) {
            try {
                Class<?> clz = ClassUtils.getClass(clzName);
                injectFields(clz, null);
                injectMethods(clz, null);
            } catch (Exception e) {
                throw new BeanInitializationException("Failed to inject static class " + clzName, e);
            }
        }
    }
    
    public List<String> getStaticClazzs() {
        return staticClazzs;
    }
    
    public void setStaticClazzs(List<String> staticClazzs) {
        this.staticClazzs = staticClazzs;
    }
    
}
